package ifm4j.collections.treeCollections;

import ifm4j.collections.treeCollections.factories.TreeFactory;
import ifm4j.collections.treeCollections.factories.TreeNodeType;
import ifm4j.collections.treeCollections.factories.TreeObjectFactory;
import ifm4j.utils.Identifier;
import ifm4j.utils.InvalidIdentifierException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;


public abstract class TreeObject {
    private static final String XML_SYMBOL_ID = "ID";

    private static final String XML_SYMBOL_CLASS = "Class";

    protected Identifier identifier;

    protected Tree treeOwner;

    protected TreeObject(Identifier id) {
        this.identifier = id;
    }

    /**
     * Get name of current tree object. the name for XML
     * 
     * @return
     */
    public String getName() {
        String fullClassName = this.getClass().getName();
        int dotLoc = fullClassName.lastIndexOf('.');
        if (dotLoc == -1) {
            return fullClassName;
        }

        return fullClassName.substring(dotLoc + 1);
    }

    /**
     * Get id of tree object.
     * This methods cannot override.
     * 
     * @return Tree Object id
     */
    public final Identifier getIdentifier() {
        return this.identifier;
    }

    /**
     * Get Tree owner
     * This method cannot override.
     * 
     * @return
     */
    public final Tree getTreeOwner() {
        return this.treeOwner;
    }

    /**
     * Set Tree owner
     * This method cannot override.
     * 
     * @param treeOwner
     */
    protected final void setTreeOwner(Tree treeOwner) {
        this.treeOwner = treeOwner;
    }

    /**
     * <p>
     * Override method
     * </p>
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (!this.getClass().equals(obj.getClass())) {
            return false;
        }
        TreeObject tmpObj = (TreeObject) obj;
        return this.identifier.equals(tmpObj.identifier);
    }

    /**
     * To string
     */
    @Override
    public String toString() {
        return String.format("%s: %s", this.getName(), this.identifier.toString());
    }

    /**
     * Turn current tree object to XML element
     * 
     * @return
     * @throws TreeCollectionException
     */
    public Element toXElem() throws TreeCollectionException {
        if (this.treeOwner == null) {
            throw new TreeCollectionException("Cannot parse to XML because the tree has no tree owner. Add the tree object into tree first.");
        }
        Document doc = this.treeOwner.getXmlDocument();
        Element xelem = doc.createElement(this.getName());

        xelem.setAttribute(XML_SYMBOL_ID, this.identifier.toString());
        xelem.setAttribute(XML_SYMBOL_CLASS, this.getClass().getName());
        return xelem;
    }

    /**
     * Parse XML element to tree object
     * 
     * @param xelem
     * @param factory
     * @return
     * @throws TreeCollectionException
     */
    public static TreeObject xmlParser(Element xelem, TreeFactory factory) throws TreeCollectionException {
        String idStr = xelem.getAttribute(XML_SYMBOL_ID);
        String classStr = xelem.getAttribute(XML_SYMBOL_CLASS);

        TreeObjectFactory treeObjFactory = factory.getObjectFactory(classStr);
        Identifier id = null;
        try {
            id = new Identifier(idStr);
        } catch (InvalidIdentifierException e) {
            throw new TreeCollectionException(
                    "Parse tree object xml exception. Invalid identifier string.");
        }
        TreeObject newObj = treeObjFactory.constructNewInstance(id, null);
        TreeNodeType basicType = treeObjFactory.getNodeType();
        if (basicType == TreeNodeType.TreeLeaf) {
            TreeLeaf.xmlParser(xelem, factory, (TreeLeaf) newObj);
        } else if (basicType == TreeNodeType.TreeNode) {
            TreeNode.xmlParser(xelem, factory, (TreeNode) newObj);
        } else if (basicType == TreeNodeType.TreeRoot) {
            TreeRoot.xmlParser(xelem, factory, (TreeRoot) newObj);
        } else {
            throw new TreeCollectionException("Invalid Tree basic type.");
        }

        treeObjFactory.xmlParser(xelem, newObj);

        return newObj;
    }
}
